﻿package net.babeuf.security 
{
	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.display.DisplayObject;
	import flash.display.GradientType;
	import flash.display.Shape;
	import flash.display.Sprite;
	import flash.filters.DisplacementMapFilter;
	import flash.geom.Matrix;
	import flash.geom.Point;
	import flash.text.TextField;
	import flash.text.TextFormat;
	
	/**
	 * Create and check Captcha security fields
	 * @author damien pegoraro
	 */
	public class Captcha extends Sprite implements ICaptcha
	{
		private var word:  String ; 
		
		private var output : Bitmap  ;
		private var bmp : BitmapData ; 
		
		private var words : Array  ;
		private var fonts : Array  ;
		
		private var bgColor : int = -1; 
		private var fgColor : int = -1; 
		
		private static const CONSONANTS :  Array = ["b", "c", "d", "f", "g", "h", "j", "k", "l", "m", "n", "p", "q", "r", "s", "t", "v", "w", "x", "z"] ;
		private static const VOWELS :  Array = ["a", "e", "i", "o", "u", "y"] ; 
		private static const D2_RUNES :  Array = ["el", "eld", "tir", "nef", "eth", "ith", "tal", "ral", "ort", "amn", "shael", "thul", "sol", "dol", "hel", "io", "lum", "ko", "fal", "lem", "pul", "um", "mal", "ist", "gul", "vex", "ohm", "lo", "sur", "ber", "jah", "cham", "zod"] ; 
		
		/**
		 * Build a new Captcha instance. This instance can be addChilded
		 * This class only does generation and check, it doesn't include the input <code>TextField</code> and other things 
		 * @param	width the graphics width for the field
		 * @param	height the graphic height for the field
		 * @param	words a list of words that can be randomly displayed
		 * @param	fonts a list of fants that can be randomly used
		 */	
		public function Captcha(width : Number, height : Number, words : Array, fonts : Array, rectTemplate : DisplayObject = null  ) 
		{
			this.words = words ; 
			this.fonts = fonts ;
			
			
			
			bmp = new BitmapData( rectTemplate ? rectTemplate.width : width, rectTemplate ? rectTemplate.height : height, true, 0) ; 
			output = new Bitmap(bmp,"auto",true) ; 
			addChild(output) ; 
			
			if ( rectTemplate ) 
			{
				x = rectTemplate.x ; 
				y = rectTemplate.y ; 
				rectTemplate.visible = false ;
				if ( rectTemplate.parent) rectTemplate.parent.addChildAt(this, rectTemplate.parent.getChildIndex(rectTemplate)) ;
			}
		}
		
		/**
		 * By default, the colors are random, but you can define them to fit graphic needs
		 * @param	background the background color
		 * @param	letters the letters color
		 */
		public function setColors( background : int = -1, letters : int  = -1)  : void
		{
			bgColor = background ;
			fgColor = letters ; 
		}
		
		/**
		 * Generates a new Captcha code a draws it into the field
		 * If your words are just syllabs, you can ask for 3 words with an empty separator to produce a single 3 syllabs word
		 * @param	words the number of words to generate 
		 * @param	spacer the spacer between each word
		 */		
		public function render( words : int = 1, spacer : String = " ") : void
		{
			word = "" ; 
			for ( var i : uint = 0 ; i < words ; ++ i )
			{
				word += getRandomWord().toUpperCase() ; 
				if (  i + 1 < words ) word += spacer ;
			}
			
			bmp.fillRect( bmp.rect, bgColor >= 0 ? to32Bits(bgColor) : Math.random() * 0x00FFFFFF + 0xFF000000) ; 
			
			
			var m : Matrix = new Matrix() ;
			m.createGradientBox(bmp.width, bmp.height, 0) ; 
			var mapClip : Shape = new Shape() ; 
			mapClip.graphics.beginGradientFill(GradientType.LINEAR, [0x000000, 0xFFFFFF, 0x000000, 0xFFFFFF, 0x000000], [1, 1, 1, 1, 1], [0, 63, 126, 199, 252],m) ; 
			mapClip.graphics.drawRect(0, 0, bmp.width, bmp.height) ; 
			 
			
			var map : BitmapData = new BitmapData(bmp.width, bmp.height,false,0) ;
			map.draw(mapClip,null,null,null,null,true) ; 
			
			var displace : DisplacementMapFilter = new DisplacementMapFilter(map,new Point(), 1, 1, Math.random()*10,Math.random() * 10+10, 'clamp');
			
			var tfClip : Sprite = new Sprite()  ;
			var font  : TextFormat = new TextFormat( getRandomFont(), 40, fgColor >= 0 ? fgColor : Math.random() * 0xFFFFFF, true) ; 			
			var tf : TextField = new TextField() ; 
			tf.embedFonts = font.font != "_sans" ; 
			tf.defaultTextFormat = font;  
			tf.text = word;  
			tf.autoSize = "left" ; 
			tf.multiline = false ;
			tf.width = 10 ;
			tf.height = 10 ;
			tf.filters = [displace] ; 
			tf.scaleX = tf.scaleY =  Math.min( (bmp.width - 10) / tf.width, (bmp.height - 10) / tf.height) ; 
			tf.x = (bmp.width - tf.width) / 2 ;
			tf.y = (bmp.height - tf.height) / 2 ;
			tfClip.addChild(tf) ; 
			m.identity() ;
			bmp.draw(tfClip, m,null,null,null,true) ; 			
		}
		
		/**
		 * You can create a new word dictionary, each dictionary is a set of all combinations between passed in vowels and consonants
		 * If no parameters are passed, the default dictionaries are taken to build the final one
		 * @param	consonants the consonants to take in account 
		 * @param	vowels the vowels to take in account 
		 * @return a dictionary made of all combinations
		 */
		public static function buildDictionary( consonants : Array = null, vowels : Array = null) : Array
		{
			if ( !consonants ) consonants = CONSONANTS ; 
			if ( !vowels ) vowels = VOWELS ; 
			
			
			var a : Array = new Array() ; 
			var l1 : int = consonants.length ; 
			var l2 : int = vowels.length ; 
			
			for ( var i : uint = 0 ;  i < l1 ; ++i )
			{
				for ( var j : uint = 0 ;  j < l2 ; ++j )
				{
					a.push( String(consonants[i] + vowels[j]).toUpperCase() ) ; 
				}				
			}		
			
			return a ;		
		}
		
		/**
		 * Build a dictionary made of Diablo II rune names
		 * @return  a dictionary made of all runes
		 */
		public static function buildD2Dictionary() : Array
		{
			return D2_RUNES; 
		}
		
		/**
		 * Check if the passed-in <code>String</code> matches the captcha value
		 * @param	value the value to check
		 * @return true if match
		 */
		public function check( value : String ) : Boolean
		{
			return value.toUpperCase() == word ;
		}
				
		private function getRandomFont(): String
		{
			return fonts[Math.floor(Math.random() * fonts.length)] ; 
		}
		
		private function getRandomWord(): String
		{
			return words[Math.floor(Math.random() * words.length)] ; 
		}
		
		
		
		private function to32Bits( color : uint ) : uint
		{
			return 0xFF000000 + color ; 
		}
	}

}